import { ref, watch, nextTick, onMounted, getCurrentInstance, reactive, onUnmounted, computed } from 'vue';
import { TerminalProps } from '@/main/js/terminalAttribute';
import { DEFAULT_COMMANDS, MESSAGE_CLASS } from '@/main/js/configuration';
import { MESSAGE_TYPE, TerminalGroupParams, TerminalLogTagEnum, TerminalCommandParams, ListenerFunTypeEnum, TerminalViewerContentParams, TriggerClickType, AskMessageType } from '@/main/js/terminalTypes';
import { _nonEmpty, _html, _defaultCommandFormatter, _eventOn, _getSelection, _copyTextToClipboard, _getClipboardText, _isParentDom, _pointInRect, _getByteLen, _eventOff, _isEmpty, _sleep } from '@/main/js/utils';
import TerminalApi, { register, unregister } from '@/main/js/terminalApi';
import Storage from '@/main/js/storage';
import LindadayoWS from '@/main/js/websocket';
import { _parseANSI } from './js/ansi/ANSI';

let idx = 0;
function generateTerminalName(init) {
  if (init) idx++;
  return `linpeng_terminal_${idx}`;
}
export default function(props: TerminalProps, {
  terminalHeaderRef,
  terminalContainerRef,
  terminalCmdInputRef,
  terminalWindowRef,
  terminalResizeLBRef,
  terminalResizeLTRef,
  terminalResizeRTRef,
  terminalResizeRBRef,
  terminalEnFlagRef,
  terminalChFlagRef,
  terminalInputPromptRef,
  terminalCursorRef,
  terminalInputBoxRef,
  terminalSearchTipsRef,
  terminalAskInputRef
}) {
  const { proxy } = getCurrentInstance();
  const headerHeight = ref(0);
  /** 命令行日志记录 */
  const terminalLog = ref<TerminalGroupParams[]>([]);
  /** 主容器样式配置 */
  const containerStyleStore = ref(null);
  /** 命令指令 */
  const command = ref('');
  /** 所有命令内容 */
  const allCommandStore = ref<TerminalCommandParams[]>([]);
  /** 终端聚合类无API名称，采用_name */
  const _name = ref('');
  /** 是否全屏状态 */
  const fullscreenState = ref(false);
  /** 显示输入命令模式光标 */
  const showInputLine = ref(true);
  /** 光标配置信息 */
  const cursorConfig = reactive({
    /** 光标默认宽度 */
    defaultWidth: 7,
    /** 光标宽度 */
    width: 7,
    left: 'unset',
    top: 'unset',
    /** 光标从第0个位置开始 */
    idx: 0,
    show: false
  });
  /** 鼠标选中文本 */
  const selectContentText = ref('');
  /** 监听窗口大小改变 */
  const resizeObserver = ref<ResizeObserver>();
  /** 字符长度配置 */
  const byteLen = reactive({
    init: false,
    enWidth: 8,
    chWidth: 13
  })
  /** 终端所属主机加后缀的实际宽高存储 */
  const inputBoxParam = reactive({
    promptWidth: 0,
    promptHeight: 0
  })
  /** 指令过滤提示 */
  const searchTips = reactive({
    open: false,
    style: {
      opacity: 100,
      top: 0,
      left: 0
    },
    cursorIdx: 0,
    items: [],
    selectedIndex: 0
  })
  /** 帮助指令提示计算 */
  const cp_helpTipConfig = computed(() => {
    const command = searchTips.items[searchTips.selectedIndex]?.command || {}
    return {
      open: !!command.key && props.enableHelpTip,
      command
    };
  });
  /** 命令行历史记录 */
  const commandHistory = ref();
  /** 服务器连接模块 */
  const isServiceConnect = ref(false);
  const terminalWebSocket = ref();
  /** 会话询问 */
  const ask = reactive({
    open: false,
    question: '',
    inputVal: '',
    // 自动回复
    autoReview: false,
    isPassword: false,
    // 会话完成回调
    callback: null,
    // 会话完成回调是否执行完毕
    isCallback: null
  });

  onMounted(() => {
    proxy.$emit('init-before', methods.getName());
    /** 初始化容器布局样式 */
    methods._initContainerStyle();
    if (props.initLog) {
      methods._newTerminalLogGroup(TerminalLogTagEnum.init);
      methods._pushMessage(props.initLog);
    }

    let _allCommandStore = []; // 初始化时所有命令
    if (props.enableDefaultCommand) {
      _allCommandStore = _allCommandStore.concat(DEFAULT_COMMANDS);
    }
    if (props.commandStore) {
      _allCommandStore = _allCommandStore.concat(props.commandStore);
    }
    allCommandStore.value = _allCommandStore;

    const el = terminalWindowRef.value;
    el.scrollTop = el.offsetHeight;

    _eventOn(window, 'click', methods.clickListener);
    /** 键盘按下监听 */
    _eventOn(window, 'keydown', methods.keydownListener);
    /** 鼠标按下选中文本 */
    _eventOn(el, 'mousedown', () => {
      const selection = _getSelection();
      let content = '';
      // 光标有选中内容
      if (!selection.isCollapsed || (content = selection.toString()).length > 0) {
        selectContentText.value = content.length > 0 ? content : selection.toString();
        selectContentText.value = selectContentText.value.replace(new RegExp(String.fromCharCode(160), 'g'), ' ');
      }
    })
    /** 鼠标右键粘贴选中内容到命令行光标处 */
    _eventOn(el, 'contextmenu', methods.contextMenuClick);
    /** 全屏时缓存终端布局样式-因为全屏后恢复会丢失 */
    let containerStyleCache = null;
    // 监听全屏事件，用户esc退出时需设置状态
    ['fullscreenchange', 'webkitfullscreenchange'].forEach(item => {
      _eventOn(window, item, () => {
        const isFullScreen = document.fullscreen || document.fullscreenElement || (document as any).webkitIsFullScreen
        if (isFullScreen) {
          containerStyleCache = JSON.parse(JSON.stringify(containerStyleStore.value));
          return;
        }
        // 退出全屏状态，控制下次双击能正常全屏
        fullscreenState.value = false;
        if (containerStyleCache) containerStyleStore.value = containerStyleCache;
      })
    })

    resizeObserver.value = new ResizeObserver(entries => {
      for (const entry of entries) {
        if (entry.target === terminalHeaderRef.value) {
          methods._updateHeaderHeight();
        }
      }
    })
    if (terminalHeaderRef.value) {
      resizeObserver.value.observe(terminalHeaderRef.value)
    }

    methods._initDrag();
    register(methods.getName('init'), methods.terminalListener);
    TerminalApi.execute(methods.getName(), 'linpeng welcome');
    // 注册命令行历史记录
    methods._initCommandHistory();
    proxy.$emit('init-complete', methods.getName());
  });

  onUnmounted(() => {
    proxy.$emit('destroyed', methods.getName());
    _eventOff(window, 'keydown', methods.keydownListener);
    _eventOff(window, 'click', methods.clickListener);
    if (resizeObserver.value && terminalHeaderRef.value) {
      resizeObserver.value.unobserve(terminalHeaderRef.value);
      resizeObserver.value = null;
    }
    unregister(methods.getName());
  })

  const methods = {
    getName(init?) {
      if (props.name) return props.name;
      _name.value = generateTerminalName(init);
      return _name.value;
    },
    /**
     * @description 是否钉上墙
     */
    isPinned() {
      return props.dragConfig && props.dragConfig.pinned;
    },
    /**
     * @description 当前终端是否活跃
     */
    isActive() {
      return cursorConfig.show
    },
    /**
     * @description 是否可以拖动
     */
    isDraggable() {
      return props.showHeader
    },
    /**
     * @description 命令分发
     * @param type 需要执行的任务
     * @param options 任务指令内容
     */
    terminalListener(type, options) {
      if (type === ListenerFunTypeEnum.pushMessage) {
        methods._pushMessage(options)
      } else if (type === ListenerFunTypeEnum.execute) {
        command.value = options.trim();
        methods._execute();
      } else {
        console.error(`Unsupported event type ${type} in instance ${methods.getName()}`)
      }
    },
    /**
     * @description 鼠标点击事件
     */
    clickListener(event: Event) {
      let activeCursor = false;
      const container = terminalContainerRef.value;
      if (container && container.getBoundingClientRect && _pointInRect(event, container.getBoundingClientRect())) {
        activeCursor = _isParentDom(event.target, container, 'tContainer')
      }
      cursorConfig.show = activeCursor;
      if (activeCursor) {
        methods._onActive();
      } else {
        methods._onInActive();
      }
    },
    /**
     * @description 键盘按下事件
     */
    keydownListener(event: KeyboardEvent) {
      /** 终端处于活跃时 */
      if (methods.isActive()) {
        try {
          const key = event.key.toLowerCase();
          if (key.match(/c|control|meta/g)) {
            if (key === 'c' && (event.metaKey || event.ctrlKey)) return;
            if (event.metaKey || event.ctrlKey) return;
          }
          if (cursorConfig.show) {
            if (key === 'tab') {
              methods._selectSearchTips();
              event.preventDefault();
              methods._closeSearchTips(false);
            } else if (document.activeElement !== terminalCmdInputRef.value) { // 当前命中dom不是命令行dom时，让光标聚焦
              terminalCmdInputRef.value.focus();
              methods._onInputKeydown(event);
            }
          }
        } finally {
          proxy.$emit('key-down', event, methods.getName());
        }
      }
    },
    /**
     * @description 鼠标右键点击事件
     */
    contextMenuClick(event: Event) {
      event.preventDefault();
      // 粘贴板无法使用时采用copySelectText
      let copySelectText = '';
      if (selectContentText.value) {
        _copyTextToClipboard(selectContentText.value);
        copySelectText = selectContentText.value.trim();
        selectContentText.value = '';
      }
      const clipboardText = _getClipboardText();
      if (clipboardText) {
        clipboardText.then(text => {
          if (!text && copySelectText) {
            text = copySelectText
          }
          text = text.trim();
          const cmd = command.value;
          command.value = cmd && cmd.length ? `${cmd}${text}` : text;
          methods._focus();
        }).catch(err => {
          console.error(err);
        })
      }
    },
    /**
     * @description 切换光标在input框中所在位置
     */
    _checkInputCursor() {
      if (terminalCmdInputRef.value.selectionStart !== cursorConfig.idx) {
        cursorConfig.idx = terminalCmdInputRef.value.selectionStart
      }
    },
    /**
     * @description 键盘按下输入
     */
    _onInputKeydown(event: KeyboardEvent) {
      const key = event.key.toLowerCase();
      if (key === 'arrowleft') {
        methods._checkInputCursor();
        /** 光标左移 */
        methods._cursorGoLeft();
      } else if (key === 'arrowright') {
        methods._checkInputCursor();
        methods._cursorGoRight();
      }
    },
    /**
     * @description 光标向左移动
     */
    _cursorGoLeft() {
      if (cursorConfig.idx > 0) {
        cursorConfig.idx--;
      }
      /** 计算光标位置坐标 */
      methods._calculateCursorPos();
    },
    /**
     * @description 光标向右移动
     */
    _cursorGoRight() {
      if (cursorConfig.idx < command.value.length) {
        cursorConfig.idx++;
      }
      methods._calculateCursorPos();
    },
    /**
     * @description 计算光标位置点信息
     * @param cmd 命令指令
     */
    _calculateCursorPos(cmd?: string) {
      // 光标需要覆盖字符的索引
      const idx = cursorConfig.idx;
      const commandStr = cmd ?? command.value;
      // 计算一个字符宽度，设置光标的默认宽度
      methods._calculateByteLen();
      if (idx < 0 || idx >= commandStr.length) {
        methods._resetCursorPos();
        return;
      }
      if (inputBoxParam.promptWidth === 0) methods._calculatePromptLen();
      /** 整个主机加输入命令的一行宽度 */
      const lineWidth = terminalInputBoxRef.value.getBoundingClientRect().width;
      const pos = { left: 0, top: 0 };
      // 当前字符宽度，默认光标宽度
      let charWidth = cursorConfig.defaultWidth;
      // 前一个字符宽度，默认终端主机加后缀宽度
      let preWidth = inputBoxParam.promptWidth;
      for (let i = 0; i <= idx; i++) {
        charWidth = methods._calculateStringWidth(commandStr[i]);
        pos.left += preWidth;
        preWidth = charWidth;
        // 代表一行命令已经输满了
        if (pos.left > lineWidth) {
          // 对应css变量--t-point-size
          pos.top += 15;
          pos.left = charWidth;
        }
      }
      // 设置光标位置信息
      cursorConfig.left = `${pos.left}px`;
      cursorConfig.top = `${pos.top}px`;
      cursorConfig.width = charWidth;
    },
    _calculateStringWidth(str: string) {
      let width = 0;
      for (const char of str) {
        // === 1 代表是英文字符，采用英文字符的宽度
        width += (_getByteLen(char) === 1 ? byteLen.enWidth : byteLen.chWidth)
      }
      return width;
    },
    /**
     * @description 计算终端主机加后缀宽高
     */
    _calculatePromptLen() {
      const prompt = terminalInputPromptRef.value;
      if (!prompt) return
      const rect = prompt.getBoundingClientRect();
      if (rect.width > 0) {
        inputBoxParam.promptWidth = rect.width;
        inputBoxParam.promptHeight = rect.height;
      }
    },
    /**
     * @description 重置光标位置信息
     */
    _resetCursorPos(cmd?: string) {
      methods._calculateByteLen();
      cursorConfig.idx = (cmd ?? command.value).length;
      cursorConfig.left = 'unset';
      cursorConfig.top = 'unset';
      cursorConfig.width = cursorConfig.defaultWidth;
    },
    /**
     * @description 计算一个字符所占宽度
     */
    _calculateByteLen() {
      if (byteLen.init) return;
      if (terminalEnFlagRef.value) {
        const rect = terminalEnFlagRef.value.getBoundingClientRect();
        if (rect && rect.width > 0) {
          byteLen.init = true;
          byteLen.enWidth = rect.width;
          byteLen.chWidth = terminalChFlagRef.value.getBoundingClientRect().width;
        }
        cursorConfig.defaultWidth = byteLen.enWidth;
      }
    },
    /**
     * @description 更新头部高度
     */
    _updateHeaderHeight() {
      nextTick(() => {
        const headerRef = terminalHeaderRef.value;
        if (headerRef && headerRef.getBoundingClientRect) {
          const rect = headerRef.getBoundingClientRect();
          headerHeight.value = rect.height;
        } else {
          headerHeight.value = 0;
        }
      })
    },
    /**
     * 新建日志记录按类型分组
     */
    _newTerminalLogGroup(tag?: TerminalLogTagEnum) {
      const newGroup: TerminalGroupParams = {
        fold: false,
        logs: []
      };
      if (tag) newGroup.tag = tag;
      terminalLog.value.push(newGroup);
      return newGroup;
    },
    /**
     * @description 根据命令类型，处理不同业务
     */
    _pushMessage(message: TerminalViewerContentParams[] | string | TerminalViewerContentParams) {
      if (!message) return;
      if (Array.isArray(message)) {
        message.forEach(child => {
          methods._pushMessage0(child)
        })
        return;
      }
      if (typeof message === 'string') {
        message = {
          type: MESSAGE_TYPE.NORMAL,
          content: message
        }
      }
      methods._pushMessage0(message);
    },
    /**
     * @description 格式化message消息，触发命令执行
     */
    _pushMessage0(message) {
      methods._filterMessageType(message);
      let terminalLogLength = terminalLog.value.length;
      if (terminalLogLength === 0) methods._newTerminalLogGroup();
      terminalLogLength = terminalLog.value.length;
      const logGroup = terminalLog.value[terminalLogLength - 1];
      logGroup.logs.push(message);
      // 10%缓存留存
      const limit = Math.floor(props.logSizeLimit * 1.1);
      if (limit > 0 && terminalLogLength > limit) {
        const left = terminalLogLength - props.logSizeLimit;
        terminalLog.value.splice(0, left);
      }
      methods._jumpToBottom();
    },
    /**
     * @description 过滤区分非法消息类型
     */
    _filterMessageType(message) {
      const valid = message.type && /^(normal|html|table|code)$/.test(message.type)
      if (!valid) {
        console.debug(`Invalid terminal message type: ${message.type}, the default type normal will be used`);
        // 非法设置为默认模式
        message.type = MESSAGE_TYPE.NORMAL
      }
      return valid;
    },
    /**
     * @description 跳转到当前命令面板最后位置处
     */
    _jumpToBottom() {
      nextTick(() => {
        const box = terminalWindowRef.value;
        if (box !== null) box.scrollTo({ top: box.scrollHeight, behavior: props.scrollMode })
      })
    },
    /**
     * @description 初始化容器样式
     */
    _initContainerStyle() {
      const containerStyStore: any = {};
      // 无法拖动模式
      if (!methods.isDraggable()) {
        containerStyStore.width = '100%';
        containerStyStore.height = '100%';
        containerStyleStore.value = containerStyStore;
        return;
      }
      const clientWidth = document.body.clientWidth;
      const clientHeight = document.body.clientHeight;
      const configWidth: number | string = props.dragConfig.width;
      let width = configWidth === null ? 700 : configWidth;
      // 拖动配置width为string&&百分比模式
      if (configWidth && typeof configWidth === 'string' && (configWidth as string).endsWith('%')) {
        width = clientWidth * (parseInt(configWidth) / 100);
      }
      const configHeight: number | string = props.dragConfig.height;
      let height = configHeight === null ? 500 : configHeight;
      if (configHeight && typeof configHeight === 'string' && (configHeight as string).endsWith('%')) {
        height = clientHeight * (parseInt(configHeight) / 100);
      }
      const zIndex = props.dragConfig.zIndex ?? 100;
      let initX: string | number, initY: string | number;
      const initPos = props.dragConfig.init;
      if (initPos && initPos.x && initPos.y) {
        initX = initPos.x;
        initY = initPos.y;
      } else {
        initX = (clientWidth - width) / 2;
        initY = (clientHeight - height) / 2;
      }
      containerStyStore.position = 'fixed'
      containerStyStore.width = width + 'px'
      containerStyStore.height = height + 'px'
      containerStyStore.left = initX + 'px'
      containerStyStore.top = initY + 'px'
      containerStyStore['z-index'] = zIndex
      containerStyleStore.value = containerStyStore;
    },
    /**
     * @description 初始化拖动配置
     */
    _initDrag() {
      if (!methods.isDraggable()) return;
      // 记录当前鼠标位置信息
      let mounseOffsetX = 0;
      let mouseOffsetY = 0;
      const dragArea = terminalHeaderRef.value;
      const box = terminalContainerRef.value;
      // 是否可以拖动
      let isDragging = false;
      // 是否可调整大小
      let isResize = false;
      const resizeData = {
        minWidth: 450,
        minHeight: 350,
        type: '',
        boxX: 0,
        boxY: 0,
        boxWidth: 0,
        boxHeight: 0,
        cursorX: 0,
        cursorY: 0
      }
      const storeResizeData = (type, evt) => {
        isResize = true
        document.body.style.userSelect = 'none'
        resizeData.type = type
        resizeData.cursorX = evt.clientX
        resizeData.cursorY = evt.clientY
        resizeData.boxX = box.offsetLeft
        resizeData.boxY = box.offsetTop
        resizeData.boxWidth = box.clientWidth
        resizeData.boxHeight = box.clientHeight
      }
      _eventOn(dragArea, 'mousedown', event => {
        methods._onActive();
        mounseOffsetX = event.clientX - box.offsetLeft;
        mouseOffsetY = event.clientY - box.offsetTop;
        isDragging = true;
        document.body.style.userSelect = 'none';
      })
      _eventOn(terminalResizeLBRef.value, 'mousedown', event => storeResizeData('lb', event))
      _eventOn(terminalResizeRTRef.value, 'mousedown', event => storeResizeData('rt', event))
      _eventOn(terminalResizeLTRef.value, 'mousedown', event => storeResizeData('lt', event))
      _eventOn(terminalResizeRBRef.value, 'mousedown', event => storeResizeData('rb', event))
      _eventOn(document, 'mousemove', event => {
        // 钉上墙 || 全屏模式 阻止拖动
        if (methods.isPinned() || fullscreenState.value) return;
        if (isDragging) {
          const moveX = event.clientX - mounseOffsetX;
          const moveY = event.clientY - mouseOffsetY;
          methods._dragging(moveX, moveY);
          return;
        } else if (isResize) {
          // 改变窗口大小模式
          let cursorX = event.clientX - resizeData.cursorX;
          let cursorY = event.clientY - resizeData.cursorY;
          const minWFun = (val) => Math.min(resizeData.boxWidth - resizeData.minWidth, val);
          const minHFun = (val) => Math.min(resizeData.boxHeight - resizeData.minHeight, val);
          const maxXFun = (val) => Math.max(0, resizeData.boxX + val);
          const maxYFun = (val) => Math.max(0, resizeData.boxY + val);
          const countMap = {
            rb: () => {
              cursorX = cursorX < 0 ? -minWFun(-cursorX) : cursorX;
              cursorY = cursorY < 0 ? -minHFun(-cursorY) : cursorY;
              containerStyleStore.value.width = `${resizeData.boxWidth + cursorX}px`
              containerStyleStore.value.height = `${resizeData.boxHeight + cursorY}px`
            },
            rt: () => {
              cursorX = cursorX < 0 ? -minWFun(-cursorX) : cursorX;
              cursorY = cursorY > 0 ? minHFun(cursorY) : cursorY;
              containerStyleStore.value.width = `${resizeData.boxWidth + cursorX}px`
              containerStyleStore.value.height = `${resizeData.boxHeight - cursorY}px`
              containerStyleStore.value.top = `${maxYFun(cursorY)}px`;
            },
            lb: () => {
              cursorX = cursorX > 0 ? minWFun(cursorX) : cursorX;
              cursorY = cursorY < 0 ? -minHFun(-cursorY) : cursorY;
              containerStyleStore.value.width = `${resizeData.boxWidth - cursorX}px`
              containerStyleStore.value.height = `${resizeData.boxHeight + cursorY}px`
              containerStyleStore.value.left = `${maxXFun(cursorX)}px`;
            },
            lt: () => {
              cursorX = cursorX > 0 ? minWFun(cursorX) : cursorX;
              cursorY = cursorY > 0 ? minHFun(cursorY) : cursorY;
              containerStyleStore.value.width = `${resizeData.boxWidth - cursorX}px`
              containerStyleStore.value.height = `${resizeData.boxHeight - cursorY}px`
              containerStyleStore.value.top = `${maxYFun(cursorY)}px`;
              containerStyleStore.value.left = `${maxXFun(cursorX)}px`;
            }
          }
          countMap[resizeData.type]();
        }
      })
      _eventOn(document, 'mouseup', () => {
        if (isDragging || isResize) methods._onActive();
        isDragging = false;
        isResize = false;
        document.body.style.userSelect = 'unset';
      })
    },
    /**
     * @description 拖动
     */
    _dragging(x: number, y: number) {
      if (methods.isPinned()) return;
      const clientWidth = document.body.clientWidth;
      const clientHeight = document.body.clientHeight;
      const container = terminalContainerRef.value;
      const xPointJudge = x > clientWidth - container.clientWidth;
      const yPointJudge = y > clientHeight - container.clientHeight;
      const xPoint = xPointJudge ? clientWidth - container.clientWidth : Math.max(0, x);
      const yPoint = yPointJudge ? clientHeight - container.clientHeight : Math.max(0, y);
      if (props.dragConfig) {
        props.dragConfig.init = {
          x: xPoint,
          y: yPoint
        }
      }
      containerStyleStore.value.left = xPoint + 'px';
      containerStyleStore.value.top = yPoint + 'px';
    },
    _fullscreen() {
      const fullArea = terminalContainerRef.value;
      if (fullscreenState.value) {
        if (document.exitFullscreen) {
          document.exitFullscreen()
        } else {
          (document as any).webkitCancelFullScreen()
        }
      } else {
        fullArea.requestFullscreen?.() ?? fullArea.webkitRequestionFullscreen();
      }
      fullscreenState.value = !fullscreenState.value;
    },
    /**
     * @description 点击事件分发
     */
    _triggerClick(key: TriggerClickType) {
      if (key === TriggerClickType.fullscreen) {
        methods._fullscreen()
      } else if (key === TriggerClickType.pin && props.showHeader) {
        const pinned = props.dragConfig.pinned || false;
        props.dragConfig.pinned = !pinned;
      }
      proxy.$emit('terminal-click', key, methods.getName());
    },
    /**
     * @description 格式化命令
     */
    _commandFormatter(cmd: string) {
      if (props.commandFormatter) return props.commandFormatter(cmd);
      return _defaultCommandFormatter(cmd);
    },
    /**
     * @description 聚焦光标，输入内容
     */
    _focus(enforceFocus = false) {
      nextTick(() => {
        let input;
        if (ask.open) {
          input = terminalAskInputRef.value;
          cursorConfig.show = false;
        } else {
          if (enforceFocus) {
            input = terminalCmdInputRef.value;
          }
          cursorConfig.show = true;
        }
        if (input) input.focus();
        methods._onActive();
      })
    },
    /**
     * @description 当前活跃的终端
     */
    _onActive() {
      proxy.$emit('active', methods.getName());
    },
    /**
     * @description 当前退出活跃的终端
     */
    _onInActive() {
      proxy.$emit('inactive', methods.getName());
    },
    /**
     * @description 退出命令执行回调
     */
    _endExecCallBack() {
      command.value = ''; // 清空命令
      cursorConfig.show = true;
    },
    /**
     * @description 存储当前指令
     */
    _saveCurCommand() {
      const group = methods._newTerminalLogGroup()
      // 保存命令到历史记录栈中(命令行非空，才保存到历史栈里)
      if (command.value) methods._addCommand(command.value);
      group.logs.push({
          type: MESSAGE_TYPE.CMDLINE,
          content: `${_html(props.context)}${props.contextSuffix}${methods._commandFormatter(command.value)}`
      });
      methods._jumpToBottom()
    },
    /**
     * @description help命令执行后操作
     * @param str 指令字符 为空获取所有指令 :local 查看指定分组有的指令 -linpeng 查看指定指令的使用方法
     */
    _printHelp(regExp, str) {
      // 表格结构化数据
      const content = {
        head: ['KEY', 'GROUP', 'DETAIL'],
        rows: []
      }
      const findGroup = str && str.length > 1 && str.startsWith(':') ? str.substring(1).toLowerCase() : null;
      const findCmd = str && str.length > 1 && str.startsWith('-') ? str.substring(1).toLowerCase() : null;
      allCommandStore.value.forEach(command => {
        if (findGroup) {
          if (_isEmpty(command.group) || findGroup !== command.group.toLocaleLowerCase()) return;
        } else if (findCmd) {
          if (findCmd !== command.key) return;
        } else if (!regExp.test(command.key)) {
          return
        }
        // 插入每一条指令内容，根据表头顺序插入
        const row = [];
        row.push(`<span class="t-cmd-key">${command.key}</span>`);
        row.push(command.group)

        let detail = '';
        if (_nonEmpty(command.description)) {
          detail += `Description: ${command.description}<br/>`;
        }
        if (_nonEmpty(command.usage)) {
          detail += `Usage: <code class="t-code-inline">${_html(command.usage)}</code><br/>`
        }
        if (command.example != null) {
          if (command.example.length > 0) {
              detail += '<br>'
          }

          for (const idx in command.example) {
              const eg = command.example[idx]
              detail += `
              <div>
                  <div class="t-cmd-help-eg">
                    eg${parseInt(idx) + 1}:
                  </div>
                  <div class="t-cmd-help-example">
                    <ul class="t-example-ul">
                      <li class="t-example-li"><code class="t-code-inline">${eg.cmd}</code></li>
                      <li class="t-example-li"><span></span></li>
                      ${_nonEmpty(eg.des) ? `<li class="t-example-li"><span>${eg.des}</span></li>` : ''}
                    </ul>
                  </div>
              </div>`
          }
          row.push(detail);
          content.rows.push(row)
        }
      })
      methods._pushMessage({
        type: MESSAGE_TYPE.TABLE,
        content: content
      })
      showInputLine.value = true;
      methods._endExecCallBack();
    },
    /**
     * @description linpeng命令 欢迎仪式
     */
    _printWelcome() {
      methods._pushMessage('欢迎使用linpeng-web-terminal，请尽情发挥吧，小优终端还在努力开发中 😀😀😀');
      showInputLine.value = true;
      methods._endExecCallBack();
    },
    /**
     * clear清除指令控制逻辑
     * @param value 被分割后的命令值
     */
    _clearControl(value) {
      const clearCommand = value.split(' '); // 拆分命令
      if (clearCommand.length === 1) {
        methods._clearAllContent();
      } else {
        switch (clearCommand[1]) {
          case '-h':
          case '--history':
            methods._clearCommandHistory()
            break;
        }
      }
    },
    /**
     * @description 清空内容
     */
    _clearAllContent() {
      // 清空命令记录（保留版本信息）
      terminalLog.value = terminalLog.value.slice(0, 1);
      showInputLine.value = true;
      methods._endExecCallBack();
    },
    /**
     * 清空命令行历史记录栈
     */
    _clearCommandHistory() {
      commandHistory.value?.clearCommand();
      showInputLine.value = true;
      methods._endExecCallBack();
    },
    /**
     * @description 返回当前日期及星期
     */
    _showCurrentDate() {
      const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
      const today = new Date();
      const weekday = today.getDay();
      const hours = today.getHours();
      const minutes = (today.getMinutes() < 10 ? '0' : '') + today.getMinutes();
      const seconds = (today.getSeconds() < 10 ? '0' : '') + today.getSeconds();
      methods._pushMessage({
        type: MESSAGE_TYPE.NORMAL,
        content: `${new Date().toLocaleDateString()} ${hours}:${minutes}:${seconds} ${weekdays[weekday]}`
      });
      showInputLine.value = true;
      methods._endExecCallBack();
    },
    /**
     * @description 打开外部链接操作
     * @param url string 链接地址
     */
    _openUrl(url) {
      // 判断是否输入链接
      if (url) {
        // 判断链接是否符合规则
        if (methods._isRightURL(url)) {
          if (url.startsWith('http')) {
            window.open(url, '_blank');
          } else {
            window.open('http://' + url, '_blank');
          }
        } else {
          methods._pushMessage({
            type: MESSAGE_TYPE.NORMAL,
            class: MESSAGE_CLASS.ERROR,
            tag: MESSAGE_CLASS.ERROR.toUpperCase(),
            content: '输入地址不正确，请重新输入哟~😔😔😔'
          });
        }
      } else {
        methods._pushMessage({
          type: MESSAGE_TYPE.NORMAL,
          class: MESSAGE_CLASS.WARN,
          tag: MESSAGE_CLASS.WARN.toUpperCase(),
          content: '未输入合法链接.'
        })
      }
      showInputLine.value = true;
      methods._endExecCallBack();
    },
    /**
     * @description 判断url是否合格
     * @param str_url string 当前url
     */
    _isRightURL(str_url) {
      const urlAndIpRegex = /^(?:http(s)?:\/\/)?[a-zA-Z0-9]+([-.][a-zA-Z0-9]+)*\.[a-zA-Z0-9]{1,5}(:\d{1,5})?(\/.*)?([?&]\w+=\w*)?$/;
      return urlAndIpRegex.test(str_url);
    },
    /**
     * 高亮代码控制逻辑
     * @param value 被分割后的命令值
     */
    async _showCode(value) {
      const showCodeCommand = value.split(' '); // 拆分命令
      const commandVal = {
        type: MESSAGE_TYPE.CODE,
        content: '',
        class: '',
        tag: ''
      };
      switch (showCodeCommand[1]) {
        case '-c':
        case '--clipboard': {
          commandVal.content = await navigator.clipboard.readText();
          if (!commandVal.content) {
            commandVal.content = '请确保剪贴板不为空！';
            commandVal.class = MESSAGE_CLASS.WARN;
            commandVal.tag = MESSAGE_CLASS.WARN.toUpperCase();
            commandVal.type = MESSAGE_TYPE.NORMAL;
          }
          break;
        }
        default:
          commandVal.content = '请输入合法代码展示指令！';
          commandVal.class = MESSAGE_CLASS.WARN;
          commandVal.tag = MESSAGE_CLASS.WARN.toUpperCase();
          commandVal.type = MESSAGE_TYPE.NORMAL;
          break;
      }
      methods._pushMessage(commandVal);
      showInputLine.value = true;
      methods._endExecCallBack();
    },
    /*
     * @description 连接服务器终端
     */
    async _connectService(cmd) {
      const isValidate = methods._isRightURL(cmd);
      if (!isValidate) {
        methods._pushMessage({
          type: MESSAGE_TYPE.NORMAL,
          class: MESSAGE_CLASS.WARN,
          tag: MESSAGE_CLASS.WARN.toUpperCase(),
          content: 'Illegal terminal address'
        })
        methods._endExecCallBack();
        return;
      }
      showInputLine.value = false;
      methods._endExecCallBack();
      const adminRoot = {
        user: '',
        password: '',
        host: cmd
      }
      const message1 = await methods._onSendAsk({
        isPassword: false,
        question: 'user name:',
        callback: (val: string) => {
          console.log('用户名', val)
          adminRoot.user = val;
          ask.open = false;
          ask.isCallback = true;
        }
      })
      if (message1 === AskMessageType.ASK_TIMEOUT) {
        methods._sendMessageTimeout();
        return;
      }
      const message2 = await methods._onSendAsk({
        isPassword: true,
        question: 'user password:',
        callback: (val: string) => {
          console.log('密码', val);
          adminRoot.password = val;
          ask.open = false;
          ask.inputVal = '';
          ask.isCallback = false;
        }
      })
      if (message2 === AskMessageType.ASK_TIMEOUT) {
        methods._sendMessageTimeout();
        return;
      }
      /** 连接中... */
      methods._pushMessage({
        type: MESSAGE_TYPE.HTML,
        content: `<div class="hamster">
        <div class="hamster__body">
          <div class="hamster__head">
            <div class="hamster__ear"></div>
            <div class="hamster__eye"></div>
            <div class="hamster__nose"></div>
          </div>
          <div class="hamster__limb hamster__limb--fr"></div>
          <div class="hamster__limb hamster__limb--fl"></div>
          <div class="hamster__limb hamster__limb--br"></div>
          <div class="hamster__limb hamster__limb--bl"></div>
          <div class="hamster__tail"></div>
        </div>
      </div>`
      })
      await _sleep(1000);
      terminalWebSocket.value = new LindadayoWS({
        url: `ws://192.168.41.60:3000?params=${JSON.stringify(adminRoot)}`,
        onopen: () => {
          isServiceConnect.value = true;
          /** 连接成功删除连接中状态 */
          terminalLog.value[terminalLog.value.length - 1].logs.pop();
        },
        onmessage: (res) => {
          if (res) {
            methods._pushMessage({
              type: MESSAGE_TYPE.HTML,
              content: _parseANSI(res)
            })
          }
          // 连接错误
          if (res.includes('SSH CONNECTION CLOSED')) {
            isServiceConnect.value = false;
          }
          showInputLine.value = true;
          methods._endExecCallBack();
        },
        onerror: () => {
          terminalLog.value[terminalLog.value.length - 1].logs.pop();
          methods._pushMessage({
            type: MESSAGE_TYPE.NORMAL,
            class: MESSAGE_CLASS.ERROR,
            tag: MESSAGE_CLASS.ERROR.toUpperCase(),
            content: 'connect error.'
          });
          showInputLine.value = true;
          methods._endExecCallBack();
        }
      })
    },
    /**
     * @description 会话超时未交互
     */
    _sendMessageTimeout() {
      ask.open = false;
      ask.inputVal = '';
      ask.question = '';
      methods._pushMessage({
        type: MESSAGE_TYPE.NORMAL,
        class: MESSAGE_CLASS.WARN,
        tag: MESSAGE_CLASS.WARN.toUpperCase(),
        content: 'Exit without operation for a long time'
      })
    },
    /**
     * @description 发起会话配置
     */
    _onSendAsk(options: Partial<typeof ask>) {
      const { isPassword, autoReview, question, callback } = options;
      ask.open = true;
      ask.inputVal = '';
      ask.isPassword = isPassword;
      ask.autoReview = autoReview;
      ask.question = question;
      ask.callback = callback;
      ask.isCallback = null;
      methods._focus();
      methods._jumpToBottom();
      return new Promise((resolve) => {
        let timeLen = 0;
        const timer = setInterval(() => {
          timeLen += 1;
          if (ask.isCallback) {
            clearInterval(timer);
            resolve(AskMessageType.ASK_RESPONSE)
          }
          if (ask.isCallback === false) {
            clearInterval(timer);
            resolve(AskMessageType.ASK_END)
          }
          // 超过10秒判定为长时间未操作
          if (timeLen >= 10) {
            clearInterval(timer);
            resolve(AskMessageType.ASK_TIMEOUT)
          }
        }, 1000)
      })
    },
    /**
     * @description 向服务器终端发送指令
     * @param cmd 指令
     */
    _serverSendMessage(cmd) {
      terminalWebSocket.value.send(cmd);
    },
    /**
     * @description 会话确认
     */
    async _onAskInput(event: KeyboardEvent) {
      if (event.key === 'Enter') {
        if (ask.autoReview) {
          methods._pushMessage(`${ask.question}${ask.isPassword ? '*'.repeat(ask.inputVal.length) : ask.inputVal}`);
        }
        ask.question = '';
        if (ask.callback) {
          ask.callback(ask.inputVal);
        }
      }
    },
    /**
     * @description 执行操作
     */
    _execute() {
      console.log('执行', terminalLog.value)
      methods._closeSearchTips(true);
      methods._saveCurCommand();
      if (_nonEmpty(command.value)) {
        try {
          const split = command.value.split(' '); // 拆分命令
          const cmdKey = split[0]; // 命令名称
          // 执行命令前回调
          proxy.$emit('before-exec-cmd', cmdKey, methods.getName());
          /** 抛出外部进行自定义指令 */
          const execute = () => {
            showInputLine.value = false;
            const success = (message) => {
              const finish = () => {
                showInputLine.value = true;
                methods._endExecCallBack();
              };
              if (message != null) {
                methods._pushMessage(message);
              }
              /** 命令执行完毕，恢复终端 */
              finish();
            };
            const failed = (message = 'Faild to execute.') => {
              methods._pushMessage({
                type: MESSAGE_TYPE.NORMAL,
                tag: MESSAGE_CLASS.ERROR.toUpperCase(),
                class: MESSAGE_CLASS.ERROR,
                content: message
              } as TerminalViewerContentParams);
              showInputLine.value = true;
              methods._endExecCallBack();
            };
            // 执行命令回调输出命令执行内容
            proxy.$emit('exec-cmd', cmdKey, command.value, success, failed, methods.getName());
          };
          // 默认命令模式
          if (props.enableDefaultCommand) {
            if (/[\u4e00-\u9fa5]/g.test(cmdKey)) {
              methods._pushMessage({
                type: MESSAGE_TYPE.NORMAL,
                class: MESSAGE_CLASS.ERROR,
                tag: MESSAGE_CLASS.ERROR.toUpperCase(),
                content: '小优还没有那么强大，不支持输入中文指令哦~😔😔😔'
              });
              showInputLine.value = true;
              methods._endExecCallBack();
              return;
            }
            // 已经连接至服务器终端，所有指令走服务端指令
            if (isServiceConnect.value && cmdKey !== 'unconnect') {
              methods._serverSendMessage(command.value);
              return;
            }
            switch (cmdKey) {
              case 'help': {
                let reg = `^${split.length > 1 && _nonEmpty(split[1]) ? split[1] : '*'}$`;
                reg = reg.replace(/\*/g, '.*');
                methods._printHelp(new RegExp(reg, 'i'), split[1]);
                break;
              }
              case 'linpeng': {
                methods._printWelcome();
                break;
              }
              // 当前日期
              case 'date': {
                methods._showCurrentDate();
                break;
              }
              // 清屏操作
              case 'clear':
                methods._clearControl(command.value);
                break;
              // 链接操作
              case 'openUrl':
                methods._openUrl(split[1]);
                break;
              // 代码展示操作
              case 'code':
                methods._showCode(command.value);
                break;
              case 'connect':
                methods._connectService(split[1]);
                break;
              // 断开服务端连接
              case 'unconnect':
                terminalWebSocket.value.close();
                isServiceConnect.value = false;
                showInputLine.value = true;
                methods._endExecCallBack();
                break;
              default:
                /** ======= 测试模块 ====== */
                methods._pushMessage({
                  type: MESSAGE_TYPE.NORMAL,
                  class: MESSAGE_CLASS.WARN,
                  tag: MESSAGE_CLASS.WARN.toUpperCase(),
                  content: '小优还在努力识别你的指令，请耐心等待吧😭😭😭'
                });
                showInputLine.value = true;
                methods._endExecCallBack();
                /** ======= end ========== */
                // execute();
                return;
            }
          } else {
            execute();
            return;
          }
        } catch (e) {
          console.error(e);
          methods._pushMessage({
            type: MESSAGE_TYPE.NORMAL,
            class: MESSAGE_CLASS.ERROR,
            content: _html(e.stack),
            tag: MESSAGE_CLASS.ERROR.toUpperCase()
          } as TerminalViewerContentParams)
        }
      }
    },
    /**
     * @description 输入指令键盘抬起事件
     */
    _onInputKeyUp(event: KeyboardEvent) {
      if (event.key === 'Enter') {
        methods._execute();
        return;
      }
      const key = event.key.toLowerCase()
      const code = event.code.toLowerCase()
      if (['home', 'end'].includes(key) || ['altleft'].includes(code)) {
        methods._checkInputCursor()
        methods._calculateCursorPos()
      } else if (key === 'arrowup') {
        methods._inputKeyUp();
      } else if (key === 'arrowdown') {
        methods._inputKeyDown();
      }
    },
    /**
     * @description 输入指令键盘按下事件
     */
    _onInputKeyDown(event: KeyboardEvent) {
      const key = event.key.toLowerCase();
      if (key === 'arrowleft') {
        methods._checkInputCursor();
        methods._cursorGoLeft();
      } else if (key === 'arrowright') {
        methods._checkInputCursor();
        methods._cursorGoRight();
      }
    },
    /**
     * @description 键盘上键触发事件
     */
    _inputKeyUp() {
      if (!searchTips.open) {
        command.value = methods._getPrevCommand();
        return;
      }
      let idx = searchTips.selectedIndex;
      idx > 0 ? idx-- : (idx = searchTips.items.length - 1);
      methods._switchSearchTipsSelectedIdx(idx);
    },
    /**
     * @description 键盘下键触发事件
     */
    _inputKeyDown() {
      if (!searchTips.open) {
        command.value = methods._getNextCommand();
        return;
      }
      let idx = searchTips.selectedIndex;
      idx < searchTips.items.length - 1 ? idx++ : (idx = 0);
      methods._switchSearchTipsSelectedIdx(idx);
    },
    /**
     * @description 切换选中指令
     * @param idx 自动提示框中选中的指令下标
     */
    _switchSearchTipsSelectedIdx(idx) {
      const searchViewItem = terminalSearchTipsRef.value.querySelector(`.tCmdTipsItem:nth-child(${idx + 1})`);
      if (searchViewItem) searchViewItem.scrollIntoView({ block: 'start', behavior: 'smooth' }); // 平滑滚动
      searchTips.selectedIndex = idx;
    },
    /**
     * @description 计算搜索提示框位置坐标信息
     * @param {boolean} autoOpen 是否自动打开提示框
     */
    _calculateTipsPos(autoOpen = false) {
      if (autoOpen) {
        searchTips.style.opacity = 0;
        searchTips.cursorIdx = terminalCmdInputRef.selectionStart;
        searchTips.open = true;
      }
      if (!searchTips.open) return;
      proxy.$nextTick(() => {
        const cursorRect = terminalCursorRef.value.getBoundingClientRect();
        const containerRect = terminalContainerRef.value.getBoundingClientRect();
        const searchTipsRect = terminalSearchTipsRef.value.getBoundingClientRect();

        const cursorRelativeLeft = cursorRect.left - containerRect.left;
        const cursorRelativeTop = cursorRect.top - containerRect.top;

        const TOP_FLOAT = 25; // 向上定位位置浮动25个像素
        let searchTipsRelativeLeft = cursorRelativeLeft;
        let searchTipsRelativeTop = cursorRelativeTop + TOP_FLOAT;

        //  超右边界
        if (cursorRect.left + searchTipsRect.width > containerRect.left + containerRect.width) {
          searchTipsRelativeLeft -= searchTipsRect.width
        }
        //  超下边界
        if (cursorRect.top + searchTipsRect.height + TOP_FLOAT > containerRect.top + containerRect.height) {
          searchTipsRelativeTop -= (searchTipsRect.height + TOP_FLOAT)
        }
        searchTips.style.top = searchTipsRelativeTop;
        searchTips.style.left = searchTipsRelativeLeft;
        searchTips.style.opacity = 100;
      })
    },
    /**
     * @description 更新自动提示内容
     */
    _updateSearchTipsItems(items, openTips = true) {
      const flag = props.autoHelp && items && items instanceof Array && items.length > 0;
      if (!flag) {
        methods._closeSearchTips(true);
        return;
      }
      searchTips.items = items;
      searchTips.selectedIndex = 0;
      if (openTips) {
        methods._calculateTipsPos(true);
      } else {
        searchTips.open = false;
      }
    },
    /**
     * @description 过滤搜索命令列表
     */
    _searchCmd() {
      if (!props.autoHelp) return;
      const firstSpaceIdx = command.value.indexOf(' ');
      // 当前光标是否在第一个位置或不包含空格时 || 当前光标在找到指令的前面
      const cursorInKey = firstSpaceIdx <= 0 || cursorConfig.idx <= firstSpaceIdx;
      const cmd = command.value.trim().split(' ')[0];
      // 空命令 || 连接到了服务器
      if (_isEmpty(cmd) || isServiceConnect.value) {
        methods._closeSearchTips(true)
        return;
      }
      // 将特殊字符转义，避免当作正则
      const reg = new RegExp(cmd.trim().replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'ig');
      const matchArr = []; // 模糊匹配指令集合
      // 遍历所有指令查找匹配的指令
      // eslint-disable-next-line
      for (const itemCmd of allCommandStore.value) {
        if (_nonEmpty(itemCmd.key)) {
          const res = itemCmd.key.match(reg) // 是否与当前指令匹配
          if (res === null) continue;
          // 计算出匹配分数，通过sort排序越大越靠前
          const score = res.index * 1000 + (cmd.length - res[0].length) + (itemCmd.key.length - res[0].length);
          matchArr.push({
            score,
            item: itemCmd,
            keyword: itemCmd.key.split(res[0]).join(`<span class="t-cmd-key">${res[0]}</span>`)
          })
        }
      }

      if (matchArr.length > 0) {
        matchArr.sort((a, b) => a.score - b.score);
        const items = matchArr.map(child => ({
          content: child.keyword,
          description: child.item.description,
          command: child.item
        }))
        methods._updateSearchTipsItems(items, cursorInKey);
      } else {
        methods._closeSearchTips(true);
      }
    },
    /**
     * @description 关闭指令提示
     * @param {boolean} clearItem 是否清除过滤的指令列表
     */
    _closeSearchTips(clearItem = true) {
      searchTips.open = false;
      if (clearItem) {
        searchTips.items = [];
        searchTips.selectedIndex = 0;
      }
    },
    /**
     * @description 设置当前命令行为选中的指令
     */
    _selectSearchTips() {
      if (!searchTips.open) return;
      const selectedItem = searchTips.items[searchTips.selectedIndex];
      command.value = selectedItem.command.key;
      methods._resetCursorPos();
      methods._jumpToBottom();
    },
    _onInput(_event: KeyboardEvent) {
      /** 命令为空清除提示过滤列表 */
      if (_isEmpty(command.value)) {
        methods._closeSearchTips(true);
      } else {
        methods._searchCmd();
      }

      methods._calculateTipsPos();

      proxy.$nextTick(() => {
        methods._checkInputCursor();
        methods._calculateCursorPos();
      })
    },
    /**
     * @description 是否展示命令折叠开关
     * @param group 当前指令日志记录
     */
    _enableFold(group: TerminalGroupParams) {
      return props.enableFold && group.tag !== TerminalLogTagEnum.init && group.logs.length > 1 && group.logs[0].type === MESSAGE_TYPE.CMDLINE;
    },
    /**
     * @description 展开命令日志记录
     * @param group 当前指令日志记录
     */
    _closeGroupFold(group: TerminalGroupParams) {
      if (props.enableFold && group.fold) group.fold = false;
    },
    /**
     * @description 初始化命令行历史记录
     */
    _initCommandHistory() {
      commandHistory.value = new Storage(methods.getName(), []);
    },
    /**
     * 新增命令行
     * @param command 命令行
     */
    _addCommand(command: string) {
      commandHistory.value?.addCommand(command);
    },
    /**
     * 下一行 => 获取指令字符
     * @returns command
     */
    _getNextCommand() {
      const command = commandHistory.value?.getCommand(1);
      return command;
    },
    /**
     * 上一行 => 获取指令字符
     * @returns command
     */
    _getPrevCommand() {
      const command = commandHistory.value?.getCommand(-1);
      return command;
    }
  }
  watch(() => props.showHeader, () => {
    methods._updateHeaderHeight();
  }, { immediate: true })

  return {
    headerHeight,
    terminalLog,
    containerStyleStore,
    showInputLine,
    command,
    cursorConfig,
    searchTips,
    cp_helpTipConfig,
    ask,
    methods
  }
}
